package com.coolingme.springframework.webmvc.servlet;

import com.coolingme.springframework.context.MeApplicationContext;
import com.coolingme.springframework.annotation.MeController;
import com.coolingme.springframework.annotation.MeRequestMapping;
import com.coolingme.springframework.annotation.MeRequestParam;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 负责任务调度，请求分发
 *
 * @author wangyue
 * @date 2020/11/10 15:40
 */
public class MeDispatcherServlet extends HttpServlet {

    /**
     * Spring核心IoC容器
     */
    private MeApplicationContext applicationContext;

    /**
     * 保存 url 和 方法的映射关系
     */
    private List<MeHandlerMapping> handlerMappings = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            // 根据URL找到对应方法并执行，委派模式
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exception,Detail:" + Collections.singletonList(e.getMessage()));
        }
    }

    /**
     * 请求路径调度
     *
     * @param req  HttpServletRequest
     * @param resp HttpServletResponse
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
        // 获取映射
        MeHandlerMapping handler = getHandler(req);
        // 未找到对应的请求路径
        if (handler == null) {
            resp.getWriter().write("404 not find!");
            return;
        }
        // 获取方法对象
        Method method = handler.getMethod();

        // 1.参数动态获取

        // url参数Map
        Map<String, String[]> parameterMap = req.getParameterMap();

        // 硬编码获取方法参数
//        Object[] params = new Object[]{
//                req, resp, parameterMap.get("name")[0]
//        };

        // 实参列表要根据形参才能决定，首先拿到形参列表
        Class<?>[] parameterTypes = method.getParameterTypes();

        // 定义实参列表
        Object[] params = new Object[parameterTypes.length];
        // 根据每个形参，获取实参
        for (int i = 0; i < params.length; i++) {
            // 形参类型
            Class<?> parameterType = parameterTypes[i];

            // 类型等于request
            if (parameterType == HttpServletRequest.class) {
                params[i] = req;
                continue;
            } else if (parameterType == HttpServletResponse.class) {
                // 类型等于response
                params[i] = resp;
                continue;
            } else if (parameterType == String.class) {
                // 获取方法的参数注解，第一个纬度为索引，第二个维度为注解，二维数组的长度等于参数的长度
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();

                // 拿到第i个形参对应的注解
                for (Annotation annotation : parameterAnnotations[i]) {
                    if (annotation instanceof MeRequestParam) {
                        // 获取MeRequestParam的value，即自定义的实参name
                        String paramName = ((MeRequestParam) annotation).value();
                        // 根据注解的name值，从url参数中获取实参值
                        if (!"".equals(paramName.trim())) {
                            String value = Arrays.toString(parameterMap.get(paramName))
                                    // 去除 []
                                    .replaceAll("\\[|\\]", "")
                                    // 去除空白
                                    .replaceAll("\\s", "");
                            params[i] = value;
                        }
                    }
                }
            }
        }

        // 2.通过方法获取所在类实例
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        Object instance = this.applicationContext.getBean(beanName);

        // 3.通过反射调用方法
        method.invoke(instance, params);
    }

    /**
     * 获取路径映射
     *
     * @param req HttpServletRequest
     * @return MeHandlerMapping
     */
    private MeHandlerMapping getHandler(HttpServletRequest req) {
        // uri
        String uri = req.getRequestURI();
        // 上下文环境（项目名）
        String contextPath = req.getContextPath();
        // 请求路径,去掉项目名和重复的 /
        String url = uri.replace(contextPath, "").replaceAll("/+", "/");

        // 网站图标，直接返回
        String favicon = "/favicon.ico";
        if (favicon.equals(url)) {
            return null;
        }

        for (MeHandlerMapping handler : this.handlerMappings) {
            // 是否匹配url
            Matcher matcher = handler.getUrl().matcher(url);
            if (!matcher.matches()) {
                continue;
            }
            return handler;
        }
        return null;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1.初始化Spring核心IoC容器
        applicationContext = new MeApplicationContext(config.getInitParameter("contextConfigLocation"));

        //==============MVC部分==============

        // 5、初始化路径方法映射
        doInitHandlerMapping();

        System.out.println("Spring framework is init.");
    }


    /**
     * 5、初始化HandlerMapping
     * 请求路径URL 和方法Method 一对一对应
     */
    private void doInitHandlerMapping() {
        // 容器为空，不需要映射
        if (this.applicationContext.getBeanDefinitionCount() == 0) {
            return;
        }

        // 循环IOC容器
        for (String className : this.applicationContext.getBeanDefinitionNames()) {
            // 实例的类对象
            Object instance = this.applicationContext.getBean(className);
            Class<?> clazz = instance.getClass();
            // 只获取Controller
            if (!clazz.isAnnotationPresent(MeController.class)) {
                continue;
            }

            // 获取类上的路径
            String classUrl = "";
            if (clazz.isAnnotationPresent(MeRequestMapping.class)) {
                classUrl = clazz.getAnnotation(MeRequestMapping.class).value();
            }

            // 默认获取public的方法
            for (Method method : clazz.getMethods()) {
                // 只获取有MeRequestMapping注解的方法
                if (!method.isAnnotationPresent(MeRequestMapping.class)) {
                    continue;
                }
                // 获取方法上的路径
                String methodUrl = method.getAnnotation(MeRequestMapping.class).value();
                // 将方法请求路径中的 * 转为 .* ,让正则匹配所有
                methodUrl = methodUrl.replaceAll("\\*", ".*");
                // 拼接URL，去除重复的 / ,如 //user//get
                String url = ("/" + classUrl + "/" + methodUrl).replaceAll("/+", "/");
                // 将url转为正则
                Pattern patternUrl = Pattern.compile(url);

                // 存入映射关系容器中
                handlerMappings.add(new MeHandlerMapping(patternUrl, instance, method));
                System.out.println("Mapped " + className + " => " + url + " : " + method.getName());
            }
        }
    }

    /**
     * 将类名首字母小写
     *
     * @param simpleName 类名
     * @return 小写类名
     */
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        // 且大写字母的ASCII码要小于小写字母的ASCII码32
        chars[0] += 32;
        return String.valueOf(chars);
    }

}
